package defectsim.agents;

import repast.simphony.parameter.Parameters;
import repast.simphony.random.RandomHelper;
import defectsim.Globals;
import defectsim.SimulationEnvironment;
import defectsim.agents.DefectFactory.DefectType;
import defectsim.material.Material;
import defectsim.material.SlipSystem;

public class DislocationSource extends DefectAgent{

	private boolean nucleation;
	private double boxLength;
	private double gridSize;
	private int nInitialDislocations;
	private int nMaximumDislocationsToMultiply;
	private int nDislocationsToMultiply;
	private int relaxationTickCount;
	private Material material;
	private Dislocation tester;
	private double orowanStress;
	private double nucleationLength;
//	private int nSlipPlanes;
	private double unitBurgersVector;
	private double lambda;
	private int tick;

	SimulationEnvironment environment = SimulationEnvironment.getInstance();

	public DislocationSource(){
		Parameters p = environment.getParameters();
		this.boxLength = (Double)p.getValue("spacelength");
		this.gridSize = (Integer)p.getValue("gridsize");
		this.nInitialDislocations = (Integer)p.getValue("nodislocations");
			
		this.nMaximumDislocationsToMultiply = (Integer)p.getValue("nodislocationsfornucleation");
		this.relaxationTickCount = (Integer)p.getValue("relaxationtickcount");
		if (relaxationTickCount < 2)
			relaxationTickCount = 2;
		this.material = environment.getMaterial();
		nucleation = false;
		
		unitBurgersVector = material.getBurgersVector()* Globals.NANOMETER_TO_MICROMETER;
		nucleationLength = 30.0 * unitBurgersVector;
		this.lambda = 5000; 
		orowanStress = 2 * material.getShearModulus() / this.lambda; // b/lambda * b = 1/lambda
		this.tick = 0;
	}
	
	@Override
	public void percept() {
//		nucleation = environment.isNucleation();
//		nSlipPlanes = (int) (boxLength / unitBurgersVector);
//		orowanStress = 0.03;
	}

	@Override
	public void mentalUpdate() {
		
		if (Math.abs(environment.getMetrics().getMeanStress()) > orowanStress)
			tick++;
		else
			tick = 0;
		
		if (tick > 10){
			nucleation = true;
			nDislocationsToMultiply = nMaximumDislocationsToMultiply;
			tick = 0;
		}
		else 
			nucleation = false;
//		System.out.println(environment.getMetrics().getMeanStress() * Globals.GIGAPASCAL_TO_MEGAPASCAL + ", " + orowanStress * Globals.GIGAPASCAL_TO_MEGAPASCAL +
//				" " + nucleation + " " + nDislocationsToMultiply);
	}

	@Override
	public void act() {
		if (environment.getCurrentTick() == 1.0)
			this.multiply(nInitialDislocations);
		else if ((environment.getCurrentTick() > relaxationTickCount) && nucleation)
//			if (environment.getCurrentTick() % 200 == 0 && environment.getCurrentTick() < 2000)	
			this.multiply(nDislocationsToMultiply);
		
	}
	
	private void multiply(int nDislocations){
		SlipSystem[] slipsys = material.getSlipSystems();
		int n = (int)(nDislocations / material.getNumberOfActiveSlipSystems());
		for (int s = 0; s < material.getNumberOfActiveSlipSystems(); s++){
			for (int i = 0; i < n / 2; i++) {
//				TODO 0.05 is greater than minimum nucleation length. compute it according to the material
				this.multiplyRandomPair(slipsys[s], nucleationLength, boxLength, 0.0, 0.0);
//				this.multiplyFrankReadPair(slipsys[s], 0.01, boxLength, 0.0, 0.0);
//				this.multiplyFrankReadPairByBurgers(slipsys[s], 0.01, boxLength, 0.0, 0.0);
			}
		}
	}
	
//	private void multiplyAtGridPoints(){
//		double gridScale = gridSize / boxLength;
//		GridValueLayer vl = environment.getValueLayer(Globals.STRESS_VALUE_LAYER);
//	
//		for (int i = 0; i < gridSize; i++) 
//			for (int j = 0; j < gridSize; j++) {
//				double stressAtGridPoint = vl.get(i ,j); 
////				System.out.printf("S: %f, Orowan: %f\n", Math.abs(stressAtGridPoint), orowanStress);
//				if (Math.abs(stressAtGridPoint) > orowanStress){
//					SimulationMetrics metrics = environment.getMetrics();
////					nDislocationsToBeMultiplied = (int)(metrics.getTotal() * environment.getTimeStep() * 
////							metrics.getMeanVelocity().x * Globals.METER_TO_NANOMETER / environment.getMaterial().getSlipPlaneSpacing());
//					nDislocationsToMultiply = (int)((Math.abs(stressAtGridPoint) / orowanStress) * 2); 
//					System.out.printf("Number of dislocations to nucleate: %d\n", nDislocationsToMultiply);
//					if (nDislocationsToMultiply > nMaximumDislocationsToMultiply)
//						nDislocationsToMultiply = nMaximumDislocationsToMultiply;
//					SlipSystem[] slipsys = material.getSlipSystems();
//					int n = (int)(nDislocationsToMultiply / material.getNumberOfActiveSlipSystems());
//					for (int s = 0; s < material.getNumberOfActiveSlipSystems(); s++){
//						for (int k = 0; k < n / 2; k++) {
//							this.multiplyRandomPair(slipsys[s], nucleationLength, gridScale, i * gridScale, j * gridScale);
//						}
//					}
//				}
//			}
//	}
	
//	private void multiplyFrankReadPair(SlipSystem slipSystem, double minimumPairLength, double maximumPairLength,
//			double... origin){
//		double gridScale = gridSize / boxLength;
//		double dx = origin[0] + findRandomLocation(minimumPairLength, maximumPairLength);
//		double dy = origin[1] + findRandomLocation(minimumPairLength, maximumPairLength);
//		double length = findRandomLocation(minimumPairLength, maximumPairLength) / 2;
//		double lengthX = length * Math.cos(Math.toRadians(slipSystem.getGlideAngle()));
//		double lengthY = length * Math.sin(Math.toRadians(slipSystem.getGlideAngle()));
//		EdgeDislocation dislocation = (EdgeDislocation) DefectFactory.createDefect(DefectType.EdgeDislocation);
//		dislocation.setSlipSystem(slipSystem);
//		double sign = 1;
//		if (findRandomLocation(minimumPairLength, maximumPairLength) > maximumPairLength / 2)
//			sign = -sign;
//		dislocation.setUnitBurgersVector(sign);
//		dislocation.setX(dx - lengthX);
//		dislocation.setY(dy - lengthY - nucleationLength);
//		dislocation.setLocationGridX((int)((dx - lengthX) * gridScale));
//		dislocation.setLocationGridY((int)((dy - lengthY - nucleationLength) * gridScale));
//		environment.nucleate(dislocation);
//		EdgeDislocation dislocation1 = (EdgeDislocation) DefectFactory.createDefect(DefectType.EdgeDislocation);
//		dislocation1.setSlipSystem(slipSystem);
//		dislocation1.setUnitBurgersVector(-sign);
//		dislocation1.setX(dx + lengthX);
//		dislocation1.setY(dy + lengthY + nucleationLength);
//		dislocation1.setLocationGridX((int)((dx + lengthX) * gridScale));
//		dislocation1.setLocationGridY((int)((dy + lengthY + nucleationLength) * gridScale));
//		environment.nucleate(dislocation1);
//	}
	
//	private void multiplyFrankReadPairByBurgers(SlipSystem slipSystem, double minimumPairLength, double maximumPairLength,
//			double... origin){
//		double gridScale = gridSize / boxLength;
//		double dx = origin[0] + unitBurgersVector * findRandomLocation(1, nSlipPlanes - 1);
//		double dy = origin[1] + unitBurgersVector * findRandomLocation(1, nSlipPlanes - 1);
//		double length = findRandomLocation(minimumPairLength, maximumPairLength) / 2;
//		double lengthX = length * Math.cos(Math.toRadians(slipSystem.getGlideAngle()));
//		double lengthY = length * Math.sin(Math.toRadians(slipSystem.getGlideAngle()));
//		EdgeDislocation dislocation = (EdgeDislocation) DefectFactory.createDefect(DefectType.EdgeDislocation);
//		dislocation.setSlipSystem(slipSystem);
//		double sign = 1.0;
//		dislocation.setUnitBurgersVector(sign);
//		dislocation.setX(dx - lengthX);
//		dislocation.setY(dy - lengthY - nucleationLength);
//		dislocation.setLocationGridX((int)((dx - lengthX) * gridScale));
//		dislocation.setLocationGridY((int)((dy - lengthY - nucleationLength) * gridScale));
//		environment.nucleate(dislocation);
//		EdgeDislocation dislocation1 = (EdgeDislocation) DefectFactory.createDefect(DefectType.EdgeDislocation);
//		dislocation1.setSlipSystem(slipSystem);
//		dislocation1.setUnitBurgersVector(-sign);
//		dislocation1.setX(dx + lengthX);
//		dislocation1.setY(dy + lengthY + nucleationLength);
//		dislocation1.setLocationGridX((int)((dx + lengthX) * gridScale));
//		dislocation1.setLocationGridY((int)((dy + lengthY + nucleationLength) * gridScale));
//		environment.nucleate(dislocation1);
//	}
	
	private void multiplyRandomPair(SlipSystem slipSystem, double minimumPairLength, double maximumPairLength,
			double... origin){
		double gridScale = gridSize / boxLength;
		double dx = origin[0] + findRandomLocation(minimumPairLength, maximumPairLength);
		double dy = origin[1] + findRandomLocation(minimumPairLength, maximumPairLength);
		EdgeDislocation dislocation = (EdgeDislocation) DefectFactory.createDefect(DefectType.EdgeDislocation);
		dislocation.setSlipSystem(slipSystem);
		double sign = 1;
		if (findRandomLocation(minimumPairLength, maximumPairLength) > maximumPairLength / 2)
			sign = -sign;
		dislocation.setUnitBurgersVector(sign);
		dislocation.setX(dx);
		dislocation.setY(dy);
		dislocation.setLocationGridX((int)(dx * gridScale));
		dislocation.setLocationGridY((int)(dy * gridScale));
		environment.nucleate(dislocation);
		dx = origin[0] + findRandomLocation(minimumPairLength, maximumPairLength);
		dy = origin[1] + findRandomLocation(minimumPairLength, maximumPairLength);
		EdgeDislocation dislocation1 = (EdgeDislocation) DefectFactory.createDefect(DefectType.EdgeDislocation);
		dislocation1.setSlipSystem(slipSystem);
		dislocation1.setUnitBurgersVector(-sign);
		dislocation1.setX(dx);
		dislocation1.setY(dy);
		dislocation1.setLocationGridX((int)(dx * gridScale));
		dislocation1.setLocationGridY((int)(dy * gridScale));
		environment.nucleate(dislocation1);
	}
	
	private double findRandomLocation(double minimum, double maximum) {
		return RandomHelper.nextDoubleFromTo(minimum, maximum);
	}
	
//	private int findRandomLocation(int minimum, int maximum) {
//		return RandomHelper.nextIntFromTo(minimum, maximum);
//	}
	
	public Dislocation getDislocationTester(){
		return tester;
	}

}
